package com.xcity.game.worldevent;

import java.io.File;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSON;
import com.xcity.Server;
import com.xcity.config.ConfigEx;
import com.xcity.db.entity.Config;
import com.xcity.db.entity.Player;
import com.xcity.db.mapper.ConfigMapper;
import com.xcity.game.buff.Buff;
import com.xcity.game.buff.BuffTemplate;
import com.xcity.game.chat.ChatService;
import com.xcity.game.common.AbstractConfirmOperation;
import com.xcity.game.common.Cause;
import com.xcity.game.common.MessageConst;
import com.xcity.game.common.PlayerPool;
import com.xcity.game.event.GameEvents;
import com.xcity.game.item.ItemException;
import com.xcity.game.item.ItemTemplate;
import com.xcity.game.mail.MailService;
import com.xcity.game.player.PlayerService;
import com.xcity.game.player.cache.PlayerCacheService;
import com.xcity.game.relation.RelationService;
import com.xcity.pomelo.PomeloRoute;
import com.xcity.pomelo.push.NoticePush;
import com.xcity.pomelo.push.msg.DotTipPush;
import com.xcity.pomelo.push.msg.TipPush;
import com.xcity.pomelo.request.WorldEventListRequest;
import com.xcity.pomelo.request.WorldEventUseItemRequest;
import com.xcity.pomelo.response.HintResponse;
import com.xcity.pomelo.response.Response;
import com.xcity.pomelo.response.WorldEventListResponse;
import com.xcity.util.DBUtils;

import naga.x.App;
import naga.x.event.Event;
import naga.x.event.EventListener;
import naga.x.game.template.TemplateService;
import naga.x.game.time.DayListener;
import naga.x.game.time.TimeUpdater;
import naga.x.net.Packet;
import naga.x.net.Session;
import naga.x.net.annotation.OPHandler;
import naga.x.net.impl.pomelo.annotation.PomeloOP;
import naga.x.service.Service;
import naga.x.service.ServiceException;
import naga.x.util.CommonUtils;
import naga.x.util.IOUtils;
import naga.x.util.IOUtils.LineReaderListener;
import naga.x.util.NumberUtils;
import naga.x.util.TimeUtils;

/**
 * 世界事件
 * @author yang.li
 *
 */
@OPHandler
public class WorldEventService implements Service, DayListener, Runnable, EventListener, MessageConst {

	public static long serverOpenTime; // 服务器开服时间
	public static long todayZeroTime; // 当天凌晨时刻
	public static int buffIdOffset = 100000; // 100000以后的id表示世界事件buff
	
	private List<WorldEventTimeTemplate> times;
	private List<WorldEventTemplate> goodEvents; // 增益事件模板
	private List<WorldEventTemplate> notGoodEvents; // 减益事件模板
	
	private static final int HISTROY_MAX_SIZE = 21;
	private List<WorldEvent> histroy = new ArrayList<WorldEvent>(HISTROY_MAX_SIZE); // 历史事件(最近21条)
	private int eventIndex;
	private byte[] events; // 一周的事件产生顺序(增益、减益)
	private WorldEventTimeTemplate timeTemplateOfThisWeek; // 本周的事件发生时间模板
	
	static class UseItemOperation extends AbstractConfirmOperation {

		Player target;
		boolean isFriend;
		int itemId;
		
		public UseItemOperation(Player ref, Player target, boolean isFriend, int itemId, String msg) {
			super(ref, msg);
			this.target = target;
			this.isFriend = isFriend;
			this.itemId = itemId;
		}

		@Override
		public void ok(Player player) {
			try {
				player.getBag().useItem(target, itemId, ITEM_USE_COUNT, Cause.WORLD_EVENT);
				sendMsg();
			} catch (ItemException e) {
				player.send(new TipPush(e.getMessage()));
			}
		}
		
		private void sendMsg() {
			String title, content;
			if (isFriend) {
				if (itemId == ITEM_BUFF_DELAY) {
					title = EXTRA_INCOME_MAIL_TITLE;
					content = MessageFormat.format(DELAY_WORLD_EVENT_TO_FRIEND, ref.getName(), new Date());
				} else {
					title = EXCEPTION_REMOVE_MAIL_TITLE;
					content = MessageFormat.format(REMOVE_WORLD_EVENT_TO_FRIEND, ref.getName(), new Date());
				}
			} else {
				title = EXCEPTION_WARN_MAIL_TITLE;
				content = itemId == ITEM_BUFF_DELAY ? DELAY_WORLD_EVENT_TO_UNFRIEND : REMOVE_WORLD_EVENT_TO_UNFRIEND;
				content = MessageFormat.format(content, new Date());
			}
			App.getApp().getServiceManager().get(MailService.class).sendSystemMail(target.getId(), title, content);
			if (target.isOnline()) {
				target.send(new TipPush(content));
			}
			ref.send(new TipPush(USE_SUCCESS));
		}
		
	}
	
	protected static final Logger LOG = LoggerFactory.getLogger(WorldEventService.class);
	
	@Override
	public void startup() throws ServiceException {
		todayZeroTime = TimeUtils.getTodayZeroTime();
		Config cfg = DBUtils.find(Config.KEY_SERVER_OPEN_DATE, ConfigMapper.class);
		if (cfg == null) {
			serverOpenTime = TimeUpdater.getInstance().now();
			DBUtils.save(cfg = new Config(Config.KEY_SERVER_OPEN_DATE, String.valueOf(serverOpenTime)), ConfigMapper.class);
		} else {
			serverOpenTime = NumberUtils.parseLong(cfg.getValue());
		}
		
		TemplateService ts = App.getApp().getServiceManager().get(TemplateService.class);
		times = ts.getTemplatesList(WorldEventTimeTemplate.class);
		ts.remove(WorldEventTimeTemplate.class);
		
		List<WorldEventTemplate> list = ts.getTemplatesList(WorldEventTemplate.class);
//		ts.remove(WorldEventTemplate.class);
		List<WorldEventTemplate> good = new ArrayList<WorldEventTemplate>(), notGood = new ArrayList<WorldEventTemplate>();
		for (int i = 0, size = list.size(); i < size; i++) {
			WorldEventTemplate temp = list.get(i);
			if (temp.getType() == WorldEventTemplate.GOOD) {
				good.add(temp);
			} else {
				notGood.add(temp);
			}
			// wrap to buff
			ts.add(BuffTemplate.class, new WorldEventBuffTemplateWrap(buffIdOffset + temp.getId(), temp));
		}
		goodEvents = new ArrayList<WorldEventTemplate>(good);
		notGoodEvents = new ArrayList<WorldEventTemplate>(notGood);
		load();
		refreshEvents();
		
		TimeUpdater.getInstance().addDayListener(this);
		App.getApp().getEventManager().register(this);
	}
	
	void load() {
		final File file = new File(((ConfigEx) App.getApp().getConfig()).localDbDir, "worldevent.txt");
		if (!file.exists()) {
			return;
		}
		byte[] state = new byte[1];
		IOUtils.read(file, IOUtils.UTF8, new LineReaderListener() {
			@Override
			public boolean readLine(String line) {
				byte s = state[0];
				if (s == 0) {
					eventIndex = NumberUtils.parseInt(line);
				} else if (s == 1) {
					events = JSON.parseObject(line, byte[].class);
				} else if (s == 2) {
					histroy.addAll(JSON.parseArray(line, WorldEvent.class));
				}
				state[0]++;
				return true;
			}
		});
	}

	@Override
	public void shutdown() throws ServiceException {
		final File file = new File(((ConfigEx) App.getApp().getConfig()).localDbDir, "worldevent.txt");
		StringBuilder buf = new StringBuilder();
		buf.append(eventIndex).append(System.lineSeparator());
		buf.append(JSON.toJSONString(NumberUtils.bytes2ints(events))).append(System.lineSeparator());
		buf.append(JSON.toJSONString(histroy));
		IOUtils.write(file, buf.toString(), IOUtils.UTF8);
	}
	
	@Override
	public String getId() {
		return "WORLD EVENT SERVICE";
	}

	/**
	 * 取第week周的template
	 * @param week 0~n
	 * @return
	 */
	public WorldEventTimeTemplate getTimeTemplateOfWeek(int week) {
		int n = times.size();
		if (week >= n) {
			week %= n;
		}
		return times.get(week);
	}
	
	/**
	 * 触发下一次事件
	 * @return
	 */
	public void triggerNextEvent() {
		byte type = events[eventIndex++ < events.length ? eventIndex : (eventIndex = 0)];
		List<WorldEventTemplate> temps = type == WorldEventTemplate.GOOD ? goodEvents : notGoodEvents;
		int index = CommonUtils.randomInt(temps.size());
		WorldEventTemplate template = temps.get(index);
		if (histroy.size() >= HISTROY_MAX_SIZE) {
			histroy.remove(0);
		}
		WorldEvent event = new WorldEvent(template);
		histroy.add(event);
		// add buff to all online players
		Collection<Player> all = App.getApp().getServiceManager().get(PlayerService.class).getOnlinePlayers();
		Packet notice = new NoticePush(NoticePush.NOTICE_MARQUEE, template.getDesc());
		Packet tip = DotTipPush.NEWS;
		for (Player player : all) {
			player.getBuffs().addBuff(buffIdOffset + template.getId());
//			// 跑马灯公告
//			player.send(notice);
//			// 红点提示
//			player.send(tip);
		}
		Server.broadcast(notice, tip);
		// notify all players
		App.getApp().getServiceManager().get(ChatService.class).sendSystemChat(template.getDesc());
		LOG.info("[WORLD EVENT]TRIGGER[" + template.getId() + "]");
	}
	
	/**
	 * 尝试刷新下一周事件
	 */
	private void refreshEvents() {
		int elapseDay = (int) ((TimeUpdater.getInstance().now() - TimeUtils.getZeroTimeOf(serverOpenTime)) / TimeUnit.DAYS.toMillis(1));
		int week = elapseDay / 7;
		WorldEventTimeTemplate temp = getTimeTemplateOfWeek(week);
		timeTemplateOfThisWeek = temp;
		if (elapseDay % 7 == 0 // 每周第一天才刷新(服务器开服第一天算第一周第一天)
				|| events == null
				|| events.length != temp.getGoodEventCount() + temp.getNotGoodEventCount()) {
			eventIndex = 0;
			byte[] eventTypes = new byte[temp.getGoodEventCount() + temp.getNotGoodEventCount()];
			// fill values
			Arrays.fill(eventTypes, 0, temp.getGoodEventCount(), WorldEventTemplate.GOOD);
			Arrays.fill(eventTypes, temp.getGoodEventCount(), eventTypes.length, WorldEventTemplate.BAD);
			// shuffle
			events = NumberUtils.shuffle(eventTypes);
			LOG.info("[WORLD EVENT]REFRESH NEXT WEEK" + Arrays.toString(events));
		}
		// schedule
		long[] times = temp.getTimePoints();
		for (long t : times) {
			long delay = todayZeroTime + t - TimeUpdater.getInstance().now();
			if (delay >= 0) {
				App.getApp().getSchedule().schedule(this, delay, TimeUnit.MILLISECONDS);
				LOG.info("[WORLD EVENT]SCHEDULE[" + delay + "]");
			} else {
				LOG.info("[WORLD EVENT]IGNORE[" + t + "]");
			}
		}
	}
	
	@Override
	public boolean dayChanged() {
		todayZeroTime = TimeUtils.getTodayZeroTime();
		refreshEvents();
		return true;
	}

	@Override
	public void run() {
		triggerNextEvent();
	}

	@Override
	public int[] getEventTypes() {
		return new int[] {
				GameEvents.EVENT_PLAYER_LOGIN,
				GameEvents.EVENT_PLAYER_LOGOUT,
				GameEvents.EVENT_ACTOR_LOADED
		};
	}

	@Override
	public void handleEvent(Event event) {
		switch (event.getType()) {
			case GameEvents.EVENT_PLAYER_LOGIN:
				playerLogin(event.getParameter(0));
				break;
			case GameEvents.EVENT_ACTOR_LOADED:
				actorLoad(event.getParameter(0));
				break;
		}
	}
	
	void playerLogin(Player player) {
		checkBuff(player);
	}
	
	void actorLoad(Player actor) {
		checkBuff(actor);
		if (!actor.isOnline()) {
			actor.update(); // force update
		}
	}
	
	void checkBuff(Player player) {
		if (histroy.isEmpty()) {
			return;
		}
		long lastWorldEventEndTime = player.getPool().getLongValue(PlayerPool.PROPERTY_LAST_WORLD_EVENT_END_TIME);
		if (lastWorldEventEndTime >= histroy.get(histroy.size() - 1).getEndTime()) {
			return;
		}
		TemplateService ts = App.getApp().getServiceManager().get(TemplateService.class);
		for (int i = 0, size = histroy.size(); i < size; i++) {
			WorldEvent event = histroy.get(i);
			if (event.getEndTime() > lastWorldEventEndTime) {
				int buffId = buffIdOffset + event.getId();
				if (player.getBuffs().getBuff(buffId) == null) {
					Buff buff = new Buff(buffId, event.getTime(), 0, ts.get(BuffTemplate.class, buffId));
					player.getBuffs().addBuff(buff);
				}
			}
		}
	}
	
	/**
	 * 检测当前是否有世界事件
	 * @return
	 */
	public boolean hasEvent() {
		return getCurrentEvent() != null;
	}
	
	public WorldEvent getCurrentEvent() {
		int size = histroy.size();
		if (size == 0) {
			return null;
		}
		WorldEvent event = histroy.get(size - 1);
		return event.validate() ? event : null;
	}
	
	@PomeloOP(PomeloRoute.WORLD_EVENT_LIST)
	protected void requestList(Session session, WorldEventListRequest req) {
		session.send(new WorldEventListResponse(req.getSerial(), histroy));
	}
	
	@PomeloOP(PomeloRoute.WORLD_EVENT_USE_ITEM)
	protected void requestUseItem(final Session session, WorldEventUseItemRequest req) {
		Player target = App.getApp().getServiceManager().get(PlayerCacheService.class).find(req.getPid());
		if (target == null) {
			session.send(new HintResponse(req.getSerial(), PLAYER_NOT_EXIST));
			return;
		}
		WorldEvent event = getCurrentEvent();
		if (event == null) {
			session.send(new HintResponse(req.getSerial(), HAVE_NO_WORLD_EVENT));
			return;
		}
		byte useType = req.getType();
		if (useType != WorldEventUseItemRequest.HELP && useType != WorldEventUseItemRequest.MAKE_TROUBE) {
			session.send(new HintResponse(req.getSerial(), ILLEGAL_OPERATION));
			return;
		}
		final Player player = session.getClient();
		boolean isFriend = App.getApp().getServiceManager().get(RelationService.class).isFriend(player, target);
		int itemId;
		if (useType == WorldEventUseItemRequest.HELP) { // 给好友帮忙
			if (!isFriend) {
				session.send(new HintResponse(req.getSerial(), DO_ACTION_ONLY_FRIEND));
				return;
			}
			itemId = event.isGood() ? ITEM_BUFF_DELAY : ITEM_REMOVE_DEBUFF;
		} else { // 给非好友捣乱
			if (isFriend) {
				session.send(new HintResponse(req.getSerial(), DO_ACTION_ONLY_NOT_FRIEND));
				return;
			}
			itemId = event.isGood() ? ITEM_REMOVE_BUFF : ITEM_BUFF_DELAY;
		}
		int diff;
		ItemTemplate item = App.getApp().getServiceManager().get(TemplateService.class).get(ItemTemplate.class, itemId);
		if ((diff = ITEM_USE_COUNT - player.getBag().countOf(itemId)) > 0) {
			session.send(new HintResponse(req.getSerial(), MessageFormat.format(USE_ITEM_WHEN_NOT_ENOUGH, ITEM_USE_COUNT, item.getName(), diff)));
			return;
		}
		// 二次确认
		UseItemOperation op = new UseItemOperation(player, target, isFriend, itemId, MessageFormat.format(USE_ITEM_CONFIRM, ITEM_USE_COUNT, item.getName()));
		op.send();
		session.send(new Response(req.getSerial()));
	}
	
	public static final int ITEM_REMOVE_DEBUFF = 90500; // 免疫卡(对好友使用,消除debuff)(帮忙)
	public static final int ITEM_REMOVE_BUFF = 90501; // 终止卡(对非好友使用,消除buff)(捣乱)
	public static final int ITEM_BUFF_DELAY = 90502; // 翻倍卡(可对好友或非好友使用,延长buff/debuff时间)(帮忙/捣乱)
	public static final int ITEM_USE_COUNT = 10;

}
